<!DOCTYPE html>
<html>
  <head>
    <title>threejs</title>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=2,maximum-scale=1,user-scalable=yes" />
    <metahttp-equiv ="X-UA-Compatible" content="IE=edge,chrome=1" />

    <style>
      html,
      body {
        padding: 0;
        margin: 0;
        overflow: hidden;
      }

      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <script type="module">
      import * as THREE from "https://cdn.skypack.dev/three@v0.129.0";
      import { OrbitControls } from "https://cdn.skypack.dev/three@v0.129.0/examples/jsm/controls/OrbitControls.js";
      import { BufferGeometryUtils } from "https://cdn.skypack.dev/three@v0.129.0/examples/jsm/utils/BufferGeometryUtils.js";
      // 三角面片生成工具
      import Delaunator from "https://cdn.skypack.dev/delaunator@5.0.0";

      // 创建渲染器
      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight); // 设置canvas画布大小
      renderer.setPixelRatio(window.devicePixelRatio); // 设置像素比，图元光栅化时采样的像素点更多（可以推断出物理像素），画面更精细
      document.body.appendChild(renderer.domElement); // canvas画布插入dom树

      // 创建场景
      var scene = new THREE.Scene();
      scene.background = new THREE.Color("#347897");

      // 辅助线
      var axisHelper = new THREE.AxisHelper(500);
      scene.add(axisHelper);

      // 添加点光源
      let light1 = new THREE.PointLight("#fff", 0.8);
      light1.position.set(2160, -1000, -3120);
      scene.add(light1);

      //环境光
      let ambient = new THREE.AmbientLight("#fff", 0.7);
      scene.add(ambient);

      // 创建相机
      var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);
      camera.position.set(-200, 530, -1070); // 设置相机位置

      // 创建控制器
      let controls = new OrbitControls(camera, renderer.domElement);
      controls.autoRotate = true;

      // 渲染
      !(function render() {
        controls.update(); // Update controls
        renderer.render(scene, camera);
        requestAnimationFrame(render);
      })();

      class CustomTriangleGeometry {
        // 构造函数
        constructor(polygon, space) {
          this.polygon = polygon; // 多边形的坐标点数组
          this.space = space; // 顶点的间距
          this.vertexArr = []; // 顶点数组
          this.indexArr = []; // 顶点索引
          this.geometry = null; // 几何体
          this.create();
        }

        create() {
          this.fillVertex();
          this.generateTriangleFace();
          this.generateGeometry();
        }

        // 填充顶点
        fillVertex() {
          let xArr = []; // polygon所有点的x坐标
          let yArr = []; // polygon所有点的y坐标
          this.polygon.forEach((coord) => {
            xArr.push(coord[0]);
            yArr.push(coord[1]);
          });

          // 排序
          xArr = xArr.sort((item2, item1) => {
            return item2 - item1;
          });
          yArr = yArr.sort((item2, item1) => {
            return item2 - item1;
          });

          // 包围矩形
          const xMin = xArr[0];
          const xMax = xArr[xArr.length - 1];
          const yMin = yArr[0];
          const yMax = yArr[yArr.length - 1];

          // 生成等间距顶点
          let columnNum = Math.ceil((xMax - xMin) / this.space);
          let rowNum = Math.ceil((yMax - yMin) / this.space);
          let fillPointsArr = [];
          for (let i = 0; i < rowNum + 1; i++) {
            for (let j = 0; j < columnNum + 1; j++) {
              fillPointsArr.push([xMin + j * this.space, yMin + i * this.space]);
            }
          }
          // 多边形内的填充点
          let inPolygonPointsArr = [];
          fillPointsArr.forEach((coord) => {
            if (this.pointInPolygon(coord, this.polygon)) {
              inPolygonPointsArr.push(coord);
            }
          });

          // 合并
          this.vertexArr = [...this.polygon, ...inPolygonPointsArr];
        }

        // 生成三角面
        generateTriangleFace() {
          let indexArr = Delaunator.from(this.vertexArr).triangles; // 三角面的顶点索引
          /**删除处于多边形外部的三角形对应顶点索引 */
          let _indexArr = [];
          for (let i = 0; i < indexArr.length; i += 3) {
            let p1 = this.vertexArr[indexArr[i]];
            let p2 = this.vertexArr[indexArr[i + 1]];
            let p3 = this.vertexArr[indexArr[i + 2]];
            let center = [(p1[0] + p2[0] + p3[0]) / 3, (p1[1] + p2[1] + p3[1]) / 3];
            if (this.pointInPolygon(center, this.polygon)) {
              _indexArr.push(indexArr[i + 2], indexArr[i + 1], indexArr[i]);
            }
          }
          this.indexArr = _indexArr;
        }

        // 判断点是否在多边形内
        pointInPolygon(point, vs) {
          let x = point[0],
            y = point[1];
          let inside = false;
          const start = 0;
          const end = vs.length;
          let len = end - start;
          for (let i = 0, j = len - 1; i < len; j = i++) {
            let xi = vs[i + start][0],
              yi = vs[i + start][1];
            let xj = vs[j + start][0],
              yj = vs[j + start][1];
            let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
            if (intersect) inside = !inside;
          }
          return inside;
        }

        // 生成几何体
        generateGeometry() {
          this.geometry = new THREE.BufferGeometry(); // 创建一个几何体
          // 设置几何体顶点索引
          this.geometry.index = new THREE.BufferAttribute(new Uint16Array(this.indexArr), 1); // 1个浮点数对应一个顶点的索引
          // 设置几何体顶点位置坐标
          const arr = [];
          this.vertexArr.forEach((coord) => {
            arr.push(coord[0], coord[1], 0);
          });
          this.geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(arr), 3)); // 3个浮点数表示一个顶点
        }
      }

      /**
       * 经纬度转地固坐标
       */
      function lon2xyz(longitude, latitude, R = 6378137) {
        var lon = (longitude * Math.PI) / 180;
        var lat = (latitude * Math.PI) / 180;

        var x = R * Math.cos(lat) * Math.cos(lon);
        var y = R * Math.cos(lat) * Math.sin(lon);
        var z = R * Math.sin(lat);
        return { x: x, y: y, z: z };
      }

      // 加载地理信息数据
      var loader = new THREE.FileLoader().setResponseType("json");
      loader.load("http://182.43.179.137:81/public/map/世界--国家级行政区域--地理信息数据--.json", function (data) {
        data.features.forEach((country, index) => {
          if (country.geometry) {
            // 格式统一
            if (country.geometry.type === "Polygon") {
              country.geometry.coordinates = [country.geometry.coordinates];
            }

            let color = "#";
            const list = ["3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e"];
            for (let i = 0; i < 6; i++) {
              const index = Math.floor(Math.random() * 11);
              color += list[index];
            }

            // 遍历
            country.geometry.coordinates.forEach((borderShapeArr) => {
              const countryGroup = new THREE.Group(); // 国家group
              scene.add(countryGroup); // 国家group加入场景

              // 公共材质
              const material = new THREE.MeshBasicMaterial({ color: color, side: THREE.DoubleSide });

              borderShapeArr.forEach((borderShape) => {
                const customGeometry = new CustomTriangleGeometry(borderShape, 3);

                // 经纬度转地固
                const arr = customGeometry.geometry.attributes.position.array;
                const points = [];
                const normals = [];
                for (let i = 0; i < arr.length; i += 3) {
                  let point = lon2xyz(arr[i], arr[i + 1]); // 经纬度转地固坐标
                  // 姿态修正
                  let { x, y, z } = new THREE.Vector3(point.x, point.y, point.z).applyAxisAngle(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
                  points.push(x / 10000, y / 10000, z / 10000);
                }

                // 修正几何体中的顶点坐标
                customGeometry.geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(points), 3)); // 3个浮点数表示一个顶点
                const mesh = new THREE.Mesh(customGeometry.geometry, material);
                countryGroup.add(mesh);
              });
            });
          }
        });
      });

      // 底图
      const g = new THREE.SphereGeometry(6378137 / 10000 / 1.005, 1000, 1000);
      const m = new THREE.MeshBasicMaterial({ color: "#260041" });
      scene.add(new THREE.Mesh(g, m));
    </script>
  </body>
</html>
